#ifndef TFTP_H
#define TFTP_H

#include <stdio.h>
#include <ctype.h> //toupper

#include <sys/stat.h> //stat

#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <err.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h> // for close
#include <sys/poll.h>
#include "constants.h"

/**
 * Represents transfer modes for tftp.
 */
typedef enum MODE {
	OCTET,
	NETASCII
} mode_type;

/** 
 * Represents tftp error codes.
 */
typedef enum ERR_CODE {
	NOT_DEFINED = 0,
	FILE_NOT_FOUND,
	ACCESS_VIOLATION,
	DISK_FULL,
	ILLEGAL_OP,
	UNKNOWN_ID,
	FILE_EXISTS,
	UNKNOWN_USER
} error_code;

/**
 * Represents tftp opcodes.
 */
enum OPCODE {
	RRQ = 1,
	WRQ,
	DATA,
	ACK,
	ERROR
};

/**
 * Creates socket and binds to it.
 * @param address address of the socket
 * @param port of the socket
 * @return created socket
 */
int get_socket_and_bind(char *address, char *port);

/**
 * @return port number from sockaddr structure
 */
in_port_t get_in_port(struct sockaddr *sa);

/**
 * @return in_addr structure from sockaddr structure
 */
void *get_in_addr(struct sockaddr *sa);

/**
 * Prints error msg from received message.
 * @param buf buffer that contains received message
 */
void print_error_msg(char *buf);

/**
 * Determines whether file exists.
 * @param filename path to the file
 * @return nonzero value if file exists, zero otherwise
 */
int does_file_exist(const char *filename);

/**
 * Determines whether file exists and is regular.
 * @param filename path to the file
 * @return nonzero value if file exists and is regular, zero otherwise
 */
int does_file_exist_and_is_regular(const char *filename);

/**
 * Determines whether directory exists.
 * @param dir path to the directory
 * @return nonzero value if dir exists, zero otherwise
 */
int does_dir_exist(const char *dir);

/**
 * Sends error message to given address.
 * @param sock socket of the communication
 * @param err_code error code of the message
 * @param msg message to be sent
 * @param addr address to send the message to
 * @param addrlen length of the address
 */
void send_error_msg_to(int sock, error_code err_code, char *msg, struct sockaddr *addr, socklen_t addrlen);

/**
 * Sends error message.
 * @param sock socket of the communication
 * @param err_code error code of the message
 * @param msg message to be sent
 */
void send_error_msg(int sock, error_code err_code, char *msg);

/**
 * Converts characters of given string to upper characters.
 * @param buf string to be changed
 */
void string_to_upper(char *buf);

/**
 * Returns socket for the given address and port and initializes res as addrinfo and res_orig for future release of the resources.
 * @param addr address to connect to
 * @param res addrinfo of the connection
 * @param res_orig pointer to the original getaddrinfo result for releasing of the resources
 * @param port port to connect to
 * @return socket for the given address and port. 
 */
int get_socket(char *addr, struct addrinfo **res, struct addrinfo **res_orig, char *port);

/**
 * @return unsigned short from the buffer where it is stored in net order
 */
uint16_t get_ushort(char *buff);

/**
 * Converts number to net order and then assigns it to the buffer.
 * @param buf pointer to the buffer where the number assign to
 * @param number for the assignment
 */
void set_ushort(char *buf, uint16_t number);

/**
 * Assigns string to the buffer.
 * @param buf buffer where to assign string
 * @param arr string to be assigned
 * @param indent position where to assign string in the buffer
 * @param append_null determines whether we should append null after the string
 * @return position of the last character of the string in buffer
 */
int set_char_arr(char *buf, char *arr, int indent, int append_null);

/**
 * Sends data read from file.
 * @param buff buffer for holding message to be sent
 * @param netascii_buff buffer for holding content of the message in netascii format
 * @param sock socket of the communication
 * @param from_previous number of bytes that we have left from the previous call of send_data
 * @param f file from which to read the data
 * @param mode mode of the transfer
 * @param packet_sent number of packets already sent
 * @return size of the message sent
 */
int send_data(char *buff, char *netascii_buff, int sock, int *from_previous, FILE *f, mode_type mode, uint16_t packets_sent);

/**
 * Converts bytes in buffer from netascii format and writes them to file.
 * @param buf buffer of bytes
 * @param f file to write to
 * @param cr_at_the_end represents if the last message we received ended with cr
 * @param n size of the message
 */
void convert_from_net_and_write(char *buf, FILE *f, int *cr_at_the_end, int n);

/**
 * Compares two files.
 * @param path to the first file
 * @param path to the second file
 * @return nonzero if the files match, nonzero otherwise
 */
int compare(char *file1, char *file2);

#endif